#include "../core/primitive.h"

#include "../core/shape.h"

namespace csupbr {

    TriangleMesh::TriangleMesh(std::shared_ptr<Transform> o2w,
                               int vertices_num,
                               int faces_num,
                               const Point3f *vertices,
                               const Normal *normals,
                               const Vec2f *texCoords,
                               const Vertex *faces)
        : vertices_num(vertices_num), faces_num(faces_num),
          vertices(vertices, vertices + (size_t)vertices_num),
          normals(normals, normals + (size_t)vertices_num),
          texCoords(texCoords, texCoords + (size_t)vertices_num),
          faces(faces, faces + 3 * (size_t)(faces_num)) {
        for (int i = 0; i < vertices_num; i++) {
            this->vertices[i] = (*o2w)(this->vertices[i]);
            this->normals[i] = (*o2w)(this->normals[i]);
        }
    }
    std::vector<std::shared_ptr<Triangle>> TriangleMesh::Create(std::shared_ptr<Transform> o2w,
                                                int vertices_num,
                                                int faces_num,
                                                const Point3f *vertices,
                                                const Normal *normals,
                                                const Vec2f *texCoords,
                                                const Vertex *faces) {
        auto mesh = std::make_shared<TriangleMesh>(o2w, vertices_num, faces_num,
                                                   vertices, normals, texCoords, faces);
        std::vector<std::shared_ptr<Triangle>> ret;
        ret.reserve(mesh->faces.size() / 3);
        for (int i = 0; i < mesh->faces.size() / 3; i++) {
            ret.push_back(std::make_shared<Triangle>(mesh, i));
        }
        return ret;
    }

    bool Triangle::intersect(const Ray &ray, Intersection &isct) const {
        const Point3f &pa = mesh->vertices[mesh->faces[3 * (size_t)id].v];
        const Point3f &pb = mesh->vertices[mesh->faces[3 * (size_t)id + 1].v];
        const Point3f &pc = mesh->vertices[mesh->faces[3 * (size_t)id + 2].v];
        Vec3f e1 = pb - pa;
		Vec3f e2 = pc - pa;
		Vec3f s1 = ray.direct.cross(e2);
		float divisor = s1.dot(e1);
		if (divisor == 0.)return false;
		float invDivisor = 1.0f / divisor;
		// Calculate centroid
		Vec3f d = ray.origin - pa;
		float b1 = d.dot(s1) * invDivisor;
		if (b1 < 0.f || b1 > 1.0f)return false;
		Vec3f s2 = d.cross(e1);
		float b2 = ray.direct.dot(s2) * invDivisor;
		if (b2 < 0.f || b2 > 1.0f)return false;
		float b0 = 1.0f - b1 - b2;
		if (b0 < 0.f || b0 > 1.0f)return false;
		float tHit = e2.dot(s2) * invDivisor;
		if (tHit > ray.maxt || tHit < ray.mint)return false;
        auto pHit = ray.to(tHit);
        isct.pHit = pHit;
        Normal face_N = e1.cross(e2);
        isct.geoN = dot(face_N, ray.direct) < 0.0f ? face_N : -face_N;
        if (!mesh->normals.empty()) {
            isct.shN = normalize(mesh->normals[mesh->faces[3 * (size_t)id].n] * b0 +
                       mesh->normals[mesh->faces[3 * (size_t)id + 1].n] * b1 +
                       mesh->normals[mesh->faces[3 * (size_t)id + 2].n] * b2);
        } else {
            isct.shN = isct.geoN;
        }
        isct.shN = dot(isct.shN, ray.direct) < 0.0f ? isct.shN : -isct.shN;
        isct.geoCoord = CoordinateSystem(isct.geoN);
        isct.shCoord = CoordinateSystem(isct.shN);
		if (!mesh->texCoords.empty()) {
            isct.uv = mesh->texCoords[mesh->faces[3 * (size_t)id].t] * b0 +
                      mesh->texCoords[mesh->faces[3 * (size_t)id + 1].t] * b1 +
                      mesh->texCoords[mesh->faces[3 * (size_t)id + 2].t] * b2;
        }
        isct.uv = clamp(isct.uv, {.0f, .0f}, {1.f, 1.f});
        ray.maxt = tHit;
        return true;
    }
    bool Triangle::intersectP(const Ray &ray, bool blend_test) const {
        const Point3f &pa = mesh->vertices[mesh->faces[3 * (size_t)id].v];
        const Point3f &pb = mesh->vertices[mesh->faces[3 * (size_t)id + 1].v];
        const Point3f &pc = mesh->vertices[mesh->faces[3 * (size_t)id + 2].v];
        Vec3f e1 = pb - pa;
		Vec3f e2 = pc - pa;
		Vec3f s1 = ray.direct.cross(e2);
		float divisor = s1.dot(e1);
		if (divisor == 0.)return false;
		float invDivisor = 1.0f / divisor;
		// Calculate centroid
		Vec3f d = ray.origin - pa;
		float b1 = d.dot(s1) * invDivisor;
		if (b1 < 0.f || b1 > 1.0f)return false;
		Vec3f s2 = d.cross(e1);
		float b2 = ray.direct.dot(s2) * invDivisor;
		if (b2 < 0.f || b2 > 1.0f)return false;
		float b0 = 1.0f - b1 - b2;
		if (b0 < 0.f || b0 > 1.0f)return false;
		float tHit = e2.dot(s2) * invDivisor;
		if (tHit > ray.maxt || tHit < ray.mint)return false;
        return true;
    }
    
    BBox3f Triangle::getBBox() const {
        const Point3f &pa = mesh->vertices[mesh->faces[3 * (size_t)id].v];
        const Point3f &pb = mesh->vertices[mesh->faces[3 * (size_t)id + 1].v];
        const Point3f &pc = mesh->vertices[mesh->faces[3 * (size_t)id + 2].v];
        return BBox3f::Union(BBox3f(pa, pb), pc);
    }

}